{
 "nbformat": 4,
 "nbformat_minor": 2,
 "metadata": {
  "language_info": {
   "name": "python",
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "version": "3.7.7-final"
  },
  "orig_nbformat": 2,
  "file_extension": ".py",
  "mimetype": "text/x-python",
  "name": "python",
  "npconvert_exporter": "python",
  "pygments_lexer": "ipython3",
  "version": 3,
  "kernelspec": {
   "name": "python37764bitcvconda2603be1e67004bc6b46dca87e540e114",
   "display_name": "Python 3.7.7 64-bit ('cv': conda)"
  }
 },
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": "171.61454476190477\n0.6729982147525674\n20\n(1, 175, 250, 8)\n(1, 175, 250, 8)\n25.769462842538342\n25.76946\ntest 1 passed...\n(1, 116, 166, 8)\n(1, 116, 166, 8)\ntest 2 passed...\nYour final score:[100]\n(1, 175, 250, 8)\n(1, 88, 125, 16)\n(1, 29, 41, 24)\n"
    }
   ],
   "source": [
    "# 声明一些用到的库\n",
    "import base64\n",
    "from io import BytesIO\n",
    "import math\n",
    "import numpy as np\n",
    "import tensorflow as tf\n",
    "from matplotlib import pyplot as plt\n",
    "from PIL import Image\n",
    "from const import img\n",
    "import os\n",
    "os.environ[\"KMP_DUPLICATE_LIB_OK\"] = \"TRUE\"\n",
    "\n",
    "\n",
    "def conv2d(input, filter, stride, padding):\n",
    "    # batch x height x width x channels\n",
    "    in_s = input.shape\n",
    "    # height x width x in_channels x out_channels\n",
    "    f_s = filter.shape\n",
    "\n",
    "    temp = []\n",
    "\n",
    "    assert len(in_s) == 4, 'input size rank 4 required!'\n",
    "    assert len(f_s) == 4, 'filter size rank 4 required!'\n",
    "    assert f_s[2] == in_s[3], 'intput channels not match filter channels.'\n",
    "    assert f_s[0] >= stride and f_s[1] >= stride, 'filter should not be less than stride!'\n",
    "    assert padding in ['SAME', 'VALID'], 'padding value[{0}] not allowded!!'.format(padding)\n",
    "\n",
    "    if padding != 'VALID':\n",
    "        # tf官网的定义为padding=same的时候out_shape = math.ceil(in_shape / stride)\n",
    "        # padding=valid的时候out_shape = math.ceil((in_shape - f_shape + 1 / stride))\n",
    "        temp = np.array(in_s[1: 3]) / stride\n",
    "    else:\n",
    "        temp = (np.array(in_s[1: 3]) - np.array(f_s[: 2]) + 1) / stride\n",
    "    out_shape = (math.ceil(temp[0]), math.ceil(temp[1]))\n",
    "    out_shape = np.concatenate([in_s[:1], out_shape, f_s[-1:]])\n",
    "    output = np.zeros(out_shape)\n",
    "    # 计算padding\n",
    "    # out = (in - f + 2p) / stride + 1\n",
    "    # 2p = (out - 1) * stride - in + f\n",
    "    _2p = np.array(out_shape[1: 3] - 1) * stride - \\\n",
    "        np.array(in_s[1: 3]) + np.array(f_s[: 2])\n",
    "    # 啊啊啊，这里tensorflow的卷积居然是上左padding分配了1 右下分配了2 一开始写成 上左2 下右边1 纳闷了半天\n",
    "    lp = np.array(_2p) // 2\n",
    "    rp = np.array(_2p) - np.array(lp)\n",
    "    input2 = input\n",
    "    if(lp.all()>0 and rp.all()>0):\n",
    "        input2 = np.pad(input, ((0, 0), (lp[0], rp[0]), (lp[1], rp[1]), (0, 0)), 'constant')        \n",
    "    in_s = input2.shape\n",
    "    # 循环每个卷积核\n",
    "    for kernel in range(f_s[3]):\n",
    "        out_r = 0\n",
    "        # 逐行扫描，每次行数叠加stride，直到越界\n",
    "        for row in range(0, in_s[1], stride):\n",
    "            if(row+f_s[0] - 1 >= in_s[1]):\n",
    "                break\n",
    "            # 新的行迭代、列回到0\n",
    "            out_c = 0\n",
    "            # 每行逐列扫描，每次列数叠加stride，直到越界\n",
    "            for col in range(0, in_s[2], stride):\n",
    "                if(col+f_s[1] - 1 >= in_s[2]):\n",
    "                    break\n",
    "                # print([row+f_s[0], col+f_s[1]])\n",
    "                # 提取原图的卷积核覆盖范围\n",
    "                cover = input2[:, row:row+f_s[0], col:col+f_s[1], :]\n",
    "                output[:, out_r, out_c, kernel] = np.sum(cover * filter[:, :, :, kernel])\n",
    "                out_c += 1\n",
    "            # 每次行迭代，feature map对应行加1\n",
    "            out_r += 1\n",
    "    return output\n",
    "\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    img_b = img\n",
    "\n",
    "    inf = BytesIO(base64.b64decode(img_b))\n",
    "    img = Image.open(inf)\n",
    "    img = np.asarray(img, dtype=np.uint8)\n",
    "\n",
    "    print(img.mean())\n",
    "    img = img/255\n",
    "    print(img.mean())\n",
    "    img = np.expand_dims(img, axis=0)  # 将图像处理成为一个batch\n",
    "    # 先定义个计算图用于运行tf\n",
    "    input_tensor = tf.placeholder(\n",
    "        tf.float32, shape=[None, None, None, None], name='input')\n",
    "    filter_tensor = tf.placeholder(\n",
    "        tf.float32, shape=[None, None, None, None], name='filter')\n",
    "\n",
    "    output_tensor1 = tf.nn.conv2d(\n",
    "        input_tensor, filter_tensor, padding='SAME', strides=[1, 2, 2, 1])\n",
    "\n",
    "    output_tensor2 = tf.nn.conv2d(\n",
    "        input_tensor, filter_tensor, padding='VALID', strides=[1, 3, 3, 1])\n",
    "\n",
    "    try:\n",
    "        final_score = 0  # 这个是最终得分\n",
    "\n",
    "        filter = np.random.uniform(size=[5, 5, 3, 8])\n",
    "\n",
    "        output = conv2d(img, filter, 2, 'SAME')\n",
    "\n",
    "        with tf.Session() as sess:\n",
    "            output_tf = sess.run(\n",
    "                output_tensor1,\n",
    "                feed_dict={\n",
    "                    input_tensor: img,\n",
    "                    filter_tensor: filter\n",
    "                })\n",
    "\n",
    "        assert output.shape == output_tf.shape, 'shape mismatch [{}] vs [{}]'.format(\n",
    "            output.shape, output_tf.shape)\n",
    "        final_score += 20  # shape算对了得20分\n",
    "\n",
    "        print(final_score)\n",
    "        print(output.shape)\n",
    "        print(output_tf.shape)\n",
    "\n",
    "        print(np.mean(output))\n",
    "        print(np.mean(output_tf))\n",
    "        diff = np.mean(np.abs(output - output_tf))\n",
    "        assert diff < 1e-5, 'value mismatch [{}]'.format(\n",
    "            diff)  # 如果这一行没有报错的话，那么实现可以认为是正确的。\n",
    "        final_score += 30  # 数值算对了得30分\n",
    "\n",
    "        print('test 1 passed...')\n",
    "\n",
    "        filter = np.random.uniform(size=[5, 5, 3, 8])\n",
    "\n",
    "        output = conv2d(img, filter, 3, 'VALID')\n",
    "\n",
    "        with tf.Session() as sess:\n",
    "            output_tf = sess.run(\n",
    "                output_tensor2,\n",
    "                feed_dict={\n",
    "                    input_tensor: img,\n",
    "                    filter_tensor: filter\n",
    "                })\n",
    "        print(output.shape)\n",
    "        print(output_tf.shape)\n",
    "        assert output.shape == output_tf.shape, 'shape mismatch [{}] vs [{}]'.format(\n",
    "            output.shape, output_tf.shape)\n",
    "        final_score += 20  # shape算对了得20分\n",
    "\n",
    "        diff = np.mean(np.abs(output - output_tf))\n",
    "        assert diff < 1e-5, 'value mismatch [{}]'.format(\n",
    "            diff)  # 如果这一行没有报错的话，那么实现可以认为是正确的。\n",
    "        final_score += 30  # 数值算对了得30分\n",
    "\n",
    "        print('test 2 passed...')\n",
    "    except Exception as ex:\n",
    "        print(ex)\n",
    "\n",
    "    print('Your final score:[{}]'.format(final_score))\n",
    "\n",
    "    # input  1 x 350 x 500 x 3\n",
    "    filter1 = np.random.uniform(size=[3, 3, 3, 8])\n",
    "    padding1 = 'SAME'\n",
    "    stride1 = 2\n",
    "    output1 = conv2d(img, filter1, stride1, padding1)\n",
    "    print(output1.shape)\n",
    "    # output 1 x 175 x 250 x 8\n",
    "\n",
    "    # input 1 x 175 x 250 x 8\n",
    "    filter2 = np.random.uniform(size=[5, 5, 8, 16])\n",
    "    padding2 = 'SAME'\n",
    "    stride2 = 2\n",
    "    output2 = conv2d(output1, filter2, stride2, padding2)\n",
    "    print(output2.shape)\n",
    "    # output 1 x 88 x 125 x 16\n",
    "\n",
    "    # input 1 x 88 x 125 x 16\n",
    "    filter2 = np.random.uniform(size=[3, 3, 16, 24])\n",
    "    padding2 = 'VALID'\n",
    "    stride2 = 3\n",
    "    output2 = conv2d(output2, filter2, stride2, padding2)\n",
    "    print(output2.shape)\n",
    "    # output 1 x 29 x 41 x 24\n",
    ""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ]
}